/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: keyboard driver
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-06-03     JasonHu           Init
 */

#define NX_LOG_NAME "ps2keyboard irq"
#define NX_LOG_LEVEL NX_LOG_INFO
#include <base/log.h>

#ifdef CONFIG_NX_DRIVER_PS2_KEYBOARD_IRQ

#include <base/page.h>
#include <base/driver.h>
#include <base/malloc.h>
#include <base/debug.h>
#include <base/delay_irq.h>
#include <base/fifo.h>
#include <base/semaphore.h>
#include <base/thread.h>
#include <base/memory.h>

#include <drvfw/irq.h>

#include <interrupt.h>
#include <io.h>

#define DRV_NAME "ps2 keyboard irq driver"
#define DRV_VERSION "0.1"

#define DEV_NAME "ps2keyboard_irq"

#define KBD_FIFO_MAX_SZ  32

#define KBC_READ_DATA 0x60

typedef struct HalDeviceExtension
{
    char irq;           /* irq号 */
    NX_Fifo * fifo;
    NX_Semaphore fifoSem;
    NX_Bool opened; /* irq driver opened */
} HalDeviceExtension;

NX_PRIVATE HalDeviceExtension halPs2KeyboardIrqExtension;

NX_PRIVATE NX_Error Ps2KeyboardIrqOpen(struct NX_Device *device, NX_U32 flags)
{
    HalDeviceExtension * kbd = (HalDeviceExtension *)device->extension;

    NX_LOG_D("KBD IRQ OPEN");
    NX_FifoReset(kbd->fifo);
    NX_SemaphoreInit(&kbd->fifoSem, 0);
    
    kbd->opened = NX_True;
    
    return NX_EOK;
}

NX_PRIVATE NX_Error Ps2KeyboardIrqClose(struct NX_Device *device)
{
    HalDeviceExtension * kbd = (HalDeviceExtension *)device->extension;
    NX_LOG_D("KBD IRQ CLOSE");
    /* disable irq */
    kbd->opened = NX_False;
    NX_SemaphoreInit(&kbd->fifoSem, 0);
    NX_FifoReset(kbd->fifo);
    NX_IRQ_Mask(kbd->irq);

    return NX_EOK;
}

/* read irq data */
NX_PRIVATE NX_Error Ps2KeyboardIrqRead(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    HalDeviceExtension * ext = device->extension;

    if (!len || len != 1)
    {
        return NX_EINVAL;
    }

    if (ext->opened == NX_False)
    {
        NX_LOG_W("kbd irq not open\n");
        if (outLen)
        {
            *outLen = 0;
        }
        return NX_ENORES;
    }

    NX_U8 scan_code = 0;
    if (NX_SemaphoreWait(&ext->fifoSem) != NX_EOK)
    {
        NX_LOG_D("kbd irq wait intr\n");
        if (outLen)
        {
            *outLen = 0;
        }
        return NX_EINTR;
    }

    if (!NX_FifoRead(ext->fifo, &scan_code, 1))
    {
        NX_LOG_W("kbd irq fifo no data\n");
        if (outLen)
        {
            *outLen = 0;
        }
        return NX_ENORES;
    }

    NX_MemCopy(buf, &scan_code, 1);

    if (outLen)
    {
        *outLen = len;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error Ps2KeyboardIrqControl(struct NX_Device *device, NX_U32 cmd, void *arg)
{
    HalDeviceExtension * ext = device->extension;

    switch (cmd)
    {
    case NX_IRQ_CMD_MASK:
        NX_IRQ_Mask(ext->irq);
        break;
    case NX_IRQ_CMD_UNMASK:
        NX_IRQ_Unmask(ext->irq);
        break;

    default:
        break;
    }
    return NX_EOK;
}

NX_PRIVATE NX_DriverOps ps2KeyboardIrqDriverOps = {
    .read       = Ps2KeyboardIrqRead,
    .open       = Ps2KeyboardIrqOpen,
    .close      = Ps2KeyboardIrqClose,
    .control    = Ps2KeyboardIrqControl,
};

NX_PRIVATE NX_Error Ps2KeyboardIrqHandler(NX_IRQ_Number irqno, void *arg)
{
    HalDeviceExtension * kbd = (HalDeviceExtension *)arg;
    NX_U8 scan_code = IO_In8(KBC_READ_DATA);
    NX_LOG_D("KBD IRQ OCCUR");
    /* save scan code to fifo buffer */
    if (kbd->opened)
    {
        NX_FifoWrite(kbd->fifo, &scan_code, 1);
        NX_SemaphoreSignal(&kbd->fifoSem);
    }
    return NX_EOK;
}

NX_PRIVATE void Ps2KeyboardIrqDriverInit(void)
{
    NX_Device *device;
    HalDeviceExtension * kbd;

    NX_Driver *driver = NX_DriverCreate(DRV_NAME, NX_DEVICE_TYPE_IRQ, 0, &ps2KeyboardIrqDriverOps);
    if (driver == NX_NULL)
    {
        NX_LOG_E("create driver failed!");
        return;
    }

    if (NX_DriverAttachDevice(driver, DEV_NAME, &device) != NX_EOK)
    {
        NX_LOG_E("attach device %s failed!", DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }

    kbd = &halPs2KeyboardIrqExtension;
    device->extension = kbd;

    kbd->opened = NX_False;
    kbd->irq = IRQ_KEYBOARD;
    kbd->fifo = NX_FifoCreate(KBD_FIFO_MAX_SZ);
    if (kbd->fifo == NX_NULL)
    {
        NX_LOG_E("create %s fifo failed!", DEV_NAME);
        
        NX_DriverDetachDevice(driver, DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
    NX_SemaphoreInit(&kbd->fifoSem, 0);
    
    NX_ASSERT(NX_IRQ_Bind(kbd->irq, Ps2KeyboardIrqHandler, kbd, "ps2Keyboard", 0) == NX_EOK);
    
    if (NX_DriverRegister(driver) != NX_EOK)
    {
        NX_LOG_E("register driver %s failed!", DRV_NAME);

        NX_FifoDestroy(kbd->fifo);

        NX_ASSERT(NX_IRQ_Mask(kbd->irq) == NX_EOK);
        NX_ASSERT(NX_IRQ_Unbind(kbd->irq, kbd) == NX_EOK);

        NX_DriverDetachDevice(driver, DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
}

NX_PRIVATE void Ps2KeyboardIrqDriverExit(void)
{
    NX_Device * dev;
    HalDeviceExtension * kbd;

    dev = NX_DeviceSearch(DEV_NAME);
    if (dev)
    {
        kbd = (HalDeviceExtension *)dev->extension;
        
        if (kbd->fifo)
        {
            NX_FifoDestroy(kbd->fifo);
        }

        NX_IRQ_Mask(kbd->irq);
        NX_IRQ_Unbind(kbd->irq, kbd);
    }
    NX_DriverCleanup(DRV_NAME);
}

NX_DRV_INIT(Ps2KeyboardIrqDriverInit);
NX_DRV_EXIT(Ps2KeyboardIrqDriverExit);

#endif /* CONFIG_NX_DRIVER_PS2_KEYBOARD_IRQ */
